﻿using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Generic;

namespace AlternativePorts
{
	public enum ReceiveMode
	{
		Sequential,
		Parallel
	}

	public class Port<T> : IPort
	{
		private Queue<PortElement<T>> q = new Queue<PortElement<T>>();
		private ReceiveMode mode = ReceiveMode.Sequential;
		private Action<T> action;
		private bool workingOnQueue;

		public int ItemCount { get { return q.Count; } }

		public void Post(T item)
		{
			var element = CreatePortElement(item);
			if (mode == ReceiveMode.Parallel)
			{
				if (action != null)
					PostParallel(element);
				else
					q.Enqueue(element);
			}
			else
				PostSeqential(element);
		}

		public void RegisterReceiver(Action<T> action, ReceiveMode mode)
		{
			this.action = action;
			this.mode = mode;
			ProcessStoredItems();
		}

		public void UnregisterReceiver()
		{
			this.action = null;
			this.mode = ReceiveMode.Parallel;
		}

		public void Send(T item)
		{
			action(item);
		}

		public void PostUnknownType(object item)
		{
			if (!(item is T))
				throw new ArgumentException("Item must be of type " + typeof(T));
			Post((T)item);
		}

		private void PostSeqential(PortElement<T> item)
		{
			q.Enqueue(item);
			WorkOnQueueSequential(true);
		}

		private void WorkOnQueueSequential(bool start)
		{
			PortElement<T> item = null;
			lock (q)
			{
				if (start && workingOnQueue)
					return;
				if (q.Count > 0 && action != null)
					item = q.Dequeue();
				else
				{
					workingOnQueue = false;
					return;
				}
				workingOnQueue = true;
			}
			ThreadPool.QueueUserWorkItem(x => {ExecuteAction(item); WorkOnQueueSequential(false); });
		}

		private void WorkOnQueueParallel()
		{
			lock (q)
			{
				while (q.Count > 0)
					PostParallel(q.Dequeue());
			}
		}

		private void PostParallel(PortElement<T> item)
		{
			ThreadPool.QueueUserWorkItem(x => ExecuteAction(item));
		}

		private void ProcessStoredItems()
		{
			if (mode == ReceiveMode.Parallel)
				WorkOnQueueParallel();
			else
				WorkOnQueueSequential(true);
		}

		private PortElement<T> CreatePortElement(T item)
		{
			var c = Causality.Current;
			var element = new PortElement<T> { Item = item };
			if (c != null)
			{
				element.ErrorPort = c.ErrorPort;
				element.CoordinationPort = c.CoordinationPort;
			}
			return element;
		}

		private void ExecuteAction(PortElement<T> element)
		{
			try
			{
				Causality c = null;
				if (element.ErrorPort != null) //oida wo is das ODER auf meiner Tastatur??
					c = new Causality(element.ErrorPort, element.CoordinationPort);

				action(element.Item);

				if (c != null)
					c.Dispose();
			}
			catch (Exception ex)
			{
				if (element.ErrorPort != null)
					element.ErrorPort.Post(ex);
			}
		}
	}
}
